<template>
  <div id="global-uploader">
    <uploader
      ref="uploader"
      :key="key"
      :options="options"
      :auto-start="true"
      :file-status-text="fileStatusText"
      class="uploader-app"
      @file-added="onFileAdded"
      @file-success="onFileSuccess"
      @file-complete="onFileComplete"
      @file-progress="onFileProgress"
      @file-error="onFileError">
      <uploader-unsupport />
      <div class="uploader-tool">
        <uploader-btn
          :class="type==='btn' ? 'uploader-button' :'uploader-text'"
          :single="multiple"
          :attrs="{accept:accept?accept:'*'}">
          <i class="el-icon-files" />{{ text }}
        </uploader-btn>
        <uploader-btn
          v-if="folder"
          :class="type==='btn' ? 'uploader-button' :'uploader-text'"
          :directory="true">
          <i class="el-icon-folder-opened" />选择文件夹
        </uploader-btn>
      </div>
      <FileItem v-if="showFileList" :files="fileList" :remove="true" @remove="removeFile" />
      <uploader-drop v-if="drop">
        <p>将文件拖拽到此处</p>
      </uploader-drop>
      <uploader-list v-show="panelShow">
        <div slot-scope="props" class="file-panel" :class="{'collapse': collapse}">
          <div class="file-title">
            <span>上传列表</span>
            <div class="operate">
              <el-button type="text" :title="collapse ? '展开':'折叠' " @click="fileListShow">
                <i id="uploadFrameBtn" :class="collapse ? 'el-icon-copy-document': 'el-icon-minus'" :style="{color:collapse ? '#FFA000':'#909399'}" />
              </el-button>
              <el-button type="text" title="关闭" @click="close">
                <i :style="{color:collapse ? '#FFA000':'#909399'}" class="el-icon-close" />
              </el-button>
            </div>
          </div>

          <ul class="file-list">
            <li v-for="file in props.fileList" :key="file.id">
              <uploader-file ref="files" :class="'file_' + file.id" :file="file" :list="true" />
            </li>
            <div v-if="!props.fileList.length" class="no-file"><i class="iconfont icon-empty-file" /> 暂无待上传文件</div>
          </ul>
        </div>
      </uploader-list>
    </uploader>

  </div>

</template>

<script>
import { getToken } from '@/utils/auth'
import $ from 'jquery'
import SparkMD5 from 'spark-md5'

export default {
  props: {
    value: {
      type: Array,
      required: false,
      default: () => {
        return []
      }
    },
    catalog: {
      type: String,
      required: true
    },
    drop: {
      type: Boolean,
      default: false
    },
    type: {
      type: String,
      default: 'btn'
    },
    folder: {
      type: Boolean,
      default: false
    },
    accept: {
      type: String,
      required: false,
      default: ''
    },
    multiple: {
      type: Boolean,
      default: true
    },
    showFileList: {
      type: Boolean,
      default: true
    },
    // 分块大小 2M
    chunkSize: {
      type: Number,
      default: 10
    },
    isChunk: {
      type: Boolean,
      default: true
    },
    text: {
      type: String,
      required: false,
      default: '选择文件'
    }

  },
  data() {
    return {
      fileList: [],
      files: [],
      key: 0,
      options: {
        target: this.$FILE_SERVER + (this.isChunk ? `file/chunkUpload?catalogId=` + this.catalog : `file/upload?catalogId=` + this.catalog),
        headers: {
          'ProjectsId': this.constants.PROJECTS_ID,
          'App-Id': this.constants.APP_ID,
          'App-Key': this.constants.APP_KEY,
          'Access-Token': getToken()
        },
        chunkSize: this.chunkSize * 1024 * 1000,
        fileParameterName: 'file',
        maxChunkRetries: 3,
        simultaneousUploads: 1,
        testChunks: this.isChunk, // 服务器分片校验
        parseTimeRemaining: (timeRemaining, parsedTimeRemaining) => {
          return parsedTimeRemaining.replace(/\syears?/, '年').replace(/\days?/, '天').replace(/\shours?/, '小时').replace(/\sminutes?/, '分钟').replace(/\sseconds?/, '秒')
        },
        checkChunkUploadedByResponse: (chunk, message) => {
          if (this.isChunk === true) {
            const res = JSON.parse(message)

            if (!res.success) {
              this.msgWarning(res.data == null ? res.message : res.data)
              return false
            }
            const chunkNumbers = res.data

            return (chunkNumbers || []).indexOf(chunk.offset + 1) >= 0
          } else {
            return true
          }
        },
        categoryMap: {
          image: ['gif', 'jpg', 'jpeg', 'png', 'bmp', 'webp'],
          video: ['mp4', 'm3u8', 'rmvb', 'avi', 'swf', '3gp', 'mkv', 'flv'],
          audio: ['mp3', 'wav', 'wma', 'ogg', 'aac', 'flac'],
          doc: ['doc'],
          xls: ['xls'],
          ppt: ['ppt'],
          pdf: ['pdf'],
          txt: ['txt'],
          rvt: ['rvt'],
          dwg: ['dwg'],
          docx: ['docx'],
          xlsx: ['xlsx'],
          pptx: ['pptx'],
          zip: ['zip', 'rar'],
          exe: ['exe']
        },
        processResponse: async(response, cb, file, chunk) => {
          const res = JSON.parse(response)
          if (res.success) {
            cb(null, response)
          } else {
            if (!file.relativePath.includes('/')) {
              cb(new Error(), (res.data == null ? res.message : res.data) || '上传失败')
            } else {
              cb(null, response)
              this.msgWarning(res.data == null ? res.message : res.data)
            }
          }
        }
      },
      attrs: {
        accept: 'image/*'
      },
      fileStatusText: {
        success: '上传成功',
        error: '上传失败',
        uploading: '上传中',
        paused: '已暂停',
        waiting: '等待中'
      },
      panelShow: false, // 选择文件后，展示上传panel
      collapse: false,
      maxSize: 1024 * 1024 * 1024 * 2
    }
  },
  computed: {
    uploader() {
      return this.$refs.uploader.uploader
    }
  },
  watch: {
    fileList: function(val) {
      if (val) {
        this.$emit('input', val)
      } else {
        this.$emit('input', [])
      }
    },
    value: function(val) {
      this.fileList = val
    }
  },
  created() {
    if (this.value.length > 0) {
      this.fileList = this.value
    }
  },
  methods: {
    onFileAdded(file) {
      // this.panelShow = true
      if (!file.relativePath.includes('/')) {
        if (file.size === 0) {
          this.msgWarning('上传文件不能为空')
          file.ignored = true
          return true
        }
        if (file.size > this.maxSize) {
          this.msgWarning('文件大小超出最大上传2GB')
          file.ignored = true
          return true
        }

        this.panelShow = true
        if (this.collapse) $('#uploadFrameBtn').trigger('click')

        this.computeMD5(file)
      }
    },
    onFileProgress(rootFile, file, chunk) {
      console.log(`上传中 ${file.name}，chunk：${chunk.startByte / 1024 / 1024} ~ ${chunk.endByte / 1024 / 1024}`)
    },
    onFileSuccess(rootFile, file, response, chunk) {
      const res = JSON.parse(response)

      // 后台报错
      if (!res.success) {
        this.msgWarning(res.data == null ? res.message : res.data)
        return
      }
      if (this.isChunk) {
        // 需要合并
        if (res.data === true) {
          // 发送merge请求，参数为identifier和filename，这个要注意需要和后台的Chunk类中的参数名对应，否则会接收不到~
          this.$store.dispatch('chunksMerge', {
            catalogId: this.catalog,
            identifier: file.uniqueIdentifier,
            filename: file.name,
            totalSize: file.size,
            fileType: file.fileType,
            loading: false
          }).then(response => {
            console.log('成功', response)
            if (!this.multiple) {
              this.fileList = []
              this.files = []
            }
            this.fileList.push(response.uuid)
            this.files.push(response)
            this.$emit('uploadResult', this.files)
          }).catch((_) => {})
        }
      } else {
        console.log('上传结果：', res.data)
        if (!this.multiple) {
          this.fileList = []
          this.files = []
        }
        this.fileList.push(res.data.uuid)
        this.files.push(res.data)
        this.$emit('uploadResult', this.files)
      }
    },
    onFileComplete(rootFile) {
    },
    onFileError(rootFile, file, response, chunk) {
      console.log(response)
      this.$message({
        message: response,
        type: 'error'
      })
    },
    fileListShow() {
      const $list = $('#global-uploader .file-list')
      if ($list.is(':visible')) {
        $list.slideUp()
        this.collapse = true
      } else {
        $list.slideDown()
        this.collapse = false
      }
    },
    /**
     * 计算md5，实现断点续传及秒传
     * @param file
     */
    computeMD5(file) {
      const fileReader = new FileReader()
      const time = new Date().getTime()
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      let currentChunk = 0
      const chunkSize = 10 * 1024 * 1000
      const chunks = Math.ceil(file.size / chunkSize)
      const spark = new SparkMD5.ArrayBuffer()
      // 文件状态设为"计算MD5"
      this.statusSet(file.id, 'md5')
      file.pause()
      loadNext()
      fileReader.onload = e => {
        spark.append(e.target.result)
        if (currentChunk < chunks) {
          currentChunk++
          loadNext()
          // 实时展示MD5的计算进度
          this.$nextTick(() => {
            $(`.myStatus_${file.id}`).text('校验MD5 ' + ((currentChunk / chunks) * 100).toFixed(0) + '%')
          })
        } else {
          const md5 = spark.end()
          this.computeMD5Success(md5, file)
          console.log(`MD5计算完毕：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${new Date().getTime() - time} ms`)
        }
      }
      fileReader.onerror = function() {
        this.error(`文件${file.name}读取出错，请检查该文件`)
        file.cancel()
      }
      function loadNext() {
        const start = currentChunk * chunkSize
        const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize
        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },
    computeMD5Success(md5, file) {
      file.uniqueIdentifier = md5
      file.resume()
      this.statusRemove(file.id)
    },
    /**
     * 新增的自定义的状态: 'md5'、'transcoding'、'failed'
     * @param id
     * @param status
     */
    statusSet(id, status) {
      const statusMap = {
        md5: {
          text: '校验MD5',
          bgc: '#fff'
        },
        merging: {
          text: '合并中',
          bgc: '#e2eeff'
        },
        transcoding: {
          text: '转码中',
          bgc: '#e2eeff'
        },
        failed: {
          text: '上传失败',
          bgc: '#e2eeff'
        }
      }
      this.$nextTick(() => {
        $(`<p class="myStatus_${id}"></p>`).appendTo(`.file_${id} .uploader-file-status`).css({
          'position': 'absolute',
          'top': '0',
          'left': '0',
          'right': '0',
          'bottom': '0',
          'zIndex': '1',
          'backgroundColor': statusMap[status].bgc
        }).text(statusMap[status].text)
      })
    },
    statusRemove(id) {
      this.$nextTick(() => {
        $(`.myStatus_${id}`).remove()
      })
    },
    error(msg) {
      this.$notify({
        title: '错误',
        message: msg,
        type: 'error',
        duration: 2000
      })
    },
    close() {
      if (this.uploader.isComplete()) {
        this.uploader.cancel()
        this.panelShow = false
      } else {
        this.$confirm('上传列表关闭,未上传成功的文件将取消上传,确定关闭?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.uploader.cancel()
          this.panelShow = false
        }).catch((_) => {})
      }
    },
    async removeFile(uuid) {
      try {
        this.$confirm('此操作将永久删除图片, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.$store.dispatch('fileResourcesRemove', uuid).then(response => {
            this.fileUUID = null
            this.msgSuccess('删除成功')
            this.fileList.splice(this.fileList.findIndex(item => item === uuid), 1)
          }).catch(() => {
            this.fileList.splice(this.fileList.findIndex(item => item === uuid), 1)
          })
        }).catch((_) => {
        })
      } catch (_) {
        console.log()
      }
    }
  }
}
</script>

<style lang="scss">
  #global-uploader {
    //min-width: 500px;
    .uploader-app {}
    .uploader-tool{
      text-align: left;
    }
    .uploader-drop{
      margin-top: 10px;
      line-height: 46px;
      text-align: center;
      border: 1px dashed #097aff;
      border-radius: 4px;
      background-color: #F0F7FD;
      color: #157EFA;
      p{
        padding: 0px;
      }
    }
    .uploader-button {
      display: inline-block;
      margin-right: 4px;
      line-height: 1;
      white-space: nowrap;
      cursor: pointer;
      background: #FFF;
      border: 1px solid #DCDFE6;
      color: #606266;
      text-align: center;
      box-sizing: border-box;
      outline: 0;
      transition: .1s;
      font-weight: 500;
      padding: 8px 20px;
      font-size: 14px;
      border-radius: 4px;
      i{
        margin-right: 5px;
      }
    }
    .uploader-text {
      display: inline-block;
      margin-right: 8px;
      line-height: 1;
      white-space: nowrap;
      cursor: pointer;
      background: #FFF;
      border: 0;
      color: #606266;
      text-align: center;
      box-sizing: border-box;
      outline: 0;
      transition: .1s;
      font-weight: 500;
      padding: 0px;
      font-size: 14px;
      border-radius: 0px;
      i{
        margin-right: 5px;
      }
    }
    .uploader-text:hover{
      color: #409eff;
      background-color: #ecf5ff;
    }
    .uploader-button:hover{
      color: #409eff;
      border-color: #c6e2ff;
      background-color: #ecf5ff;
    }
    .uploader-list{
      position: fixed;
      right: 0;
      width: 600px;
      bottom: 0px;
      z-index: 999;
    }
   .file-panel {
      background-color: #fff;
      border: 1px solid #e2e2e2;
      border-radius: 7px 7px 0 0;
      box-shadow: 0 0 10px rgba(0, 0, 0, .2);
      .file-title {
        display: flex;
        height: 40px;
        line-height: 40px;
        padding: 0 15px;
        border-bottom: 1px solid #ddd;
        font-size: 14px;
        .operate {
            flex: 1;
            text-align: right;
        }
      }
      .file-list {
        position: relative;
        height: 240px;
        overflow-x: hidden;
        overflow-y: auto;
        background-color: #fff;
        > li {
            background-color: #fff;
        }
      }
      &.collapse {
        .file-title {
            background-color: #E7ECF2;
        }
      }
      .uploader-file-name {
        text-align: left;
      }
    }
    .no-file {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 14px;
    }
    .uploader-file-icon {
      &:before {
        content: '' !important;
      }
      &[icon=image] {
        background-image: url(~@/assets/images/docImgs/jpg.png) ;
      }
      &[icon=video] {
        background-image: url(~@/assets/images/docImgs/video.png) ;
      }
      &[icon=document] {
        background-image: url(~@/assets/images/docImgs/txt.png) ;
      }
      &[icon=pdf] {
        background-image: url(~@/assets/images/docImgs/pdf.png) ;
      }
      &[icon=doc] {
        background-image: url(~@/assets/images/docImgs/doc.png) ;
      }
      &[icon=docx] {
        background-image: url(~@/assets/images/docImgs/doc.png) ;
      }
      &[icon=xls] {
        background-image: url(~@/assets/images/docImgs/xls.png) ;
      }
      &[icon=xlsx] {
        background-image: url(~@/assets/images/docImgs/xls.png) ;
      }
      &[icon=ppt] {
        background-image: url(~@/assets/images/docImgs/ppt.png) ;
      }
      &[icon=pptx] {
        background-image: url(~@/assets/images/docImgs/ppt.png) ;
      }
      &[icon=rar] {
        background-image: url(~@/assets/images/docImgs/rar.png) ;
      }
      &[icon=zip] {
        background-image: url(~@/assets/images/docImgs/zip.png) ;
      }
      &[icon=exe] {
        background-image: url(~@/assets/images/docImgs/exe.png) ;
      }
      &[icon=unknown] {
        background-image: url(~@/assets/images/docImgs/else.png) ;
      }
      &[icon=folder] {
        background-image: url(~@/assets/images/docImgs/folder.png) ;
      }

    }
  }
</style>
